﻿//==============================================================================
// File: MetaProperty.cs
// Created: 2010-05-10
// Author: Piotr Włodek
//==============================================================================
// This file is a part of MAMMOTH project.
// Copyright (C) 2010 AGH University of Science and Technology, Krakow.
// https://caribou.iisg.agh.edu.pl/trac/mammoth
//
// Mammoth is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// Mammoth is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Lesser General Public License for more details.
// 
// You should have received a copy of the GNU Lesser General Public License
// along with WpfRcp. If not, see http://www.gnu.org/licenses/.
//==============================================================================

using System;
using System.Collections.Generic;
using System.Diagnostics;
using WpfRcp.PropertyModel.Internal.Common;
using WpfRcp.PropertyModel.Scope;
using WpfRcp.PropertyModel.Validation;
using Res = WpfRcp.PropertyModel.Properties.Resources;

namespace WpfRcp.PropertyModel
{
    /// <summary>
    /// Represents a meta property which describes a property.
    /// </summary>
    [DebuggerDisplay("Name = {Name}, PropertyType = {PropertyType}, Type = {Type}, OwnerType = {OwnerType}")]
    [Serializable]
    public sealed class MetaProperty : IEquatable<MetaProperty>//, ISerializable
    {
        /// <summary>
        /// Represents an unset value.
        /// </summary>
        public static readonly object UnsetValue = new object();

        /// <summary>
        /// Represents an unknown value.
        /// </summary>
        public static readonly object UnknownValue = new object();

        /// <summary>
        /// Stores property metadata per type.
        /// </summary>
        private readonly Dictionary<Type, PropertyMetadata> m_Metadata;

        /// <summary>
        /// Contains meta property options.
        /// </summary>
        private readonly MetaPropertyOptions m_Options;

        #region Private Constructor

        private MetaProperty(string name, Type propertyType, Type ownerType, bool readOnly,
            MetaPropertyKind kind, TransientPropertyCallback callback, PropertyMetadata metadata,
            MetaPropertyOptions options)
        {
            Debug.Assert(metadata != null);

            ValidationRules = new RuleList();
            DependentTransientProperties = new List<MetaProperty>();

            Name = name;
            PropertyType = propertyType;
            OwnerType = ownerType;
            IsReadOnly = readOnly;
            Index = -1;
            Callback = callback;
            PropertyKind = kind;

            m_Metadata = new Dictionary<Type, PropertyMetadata> { { ownerType, metadata } };
            m_Options = options;
        }

        #endregion

        #region Internal Creation Methods

        internal static MetaProperty CreatePermanent(string name, Type propertyType,
            Type ownerType, bool readOnly, PropertyMetadata metadata, MetaPropertyOptions options)
        {
            return new MetaProperty(name, propertyType, ownerType, readOnly,
                MetaPropertyKind.Permanent, null, metadata, options);
        }

        internal static MetaProperty CreateTransient(string name, Type propertyType,
            Type ownerType, TransientPropertyCallback callback, PropertyMetadata metadata,
            MetaPropertyOptions options)
        {
            return new MetaProperty(name, propertyType, ownerType, true,
                MetaPropertyKind.Transient, callback, metadata, options);
        }

        internal static MetaProperty CreateDynamic(string name, Type propertyType,
            Type ownerType, PropertyMetadata metadata, MetaPropertyOptions options)
        {
            return new MetaProperty(name, propertyType, ownerType, false,
                MetaPropertyKind.Dynamic, null, metadata, options);
        }

        internal static MetaProperty CreateDynamicScoped(string name, Type propertyType,
            Type ownerType, PropertyMetadata metadata, MetaPropertyOptions options)
        {
            return new MetaProperty(name, propertyType, ownerType, false,
                MetaPropertyKind.Dynamic, null, metadata, options) { IsScoped = true };
        }

        internal static MetaProperty CreateAttached(string name, Type propertyType,
            Type ownerType, PropertyMetadata metadata, MetaPropertyOptions options)
        {
            return new MetaProperty(name, propertyType, ownerType, false,
                MetaPropertyKind.Attached, null, metadata, options);
        }

        #endregion

        /// <summary>
        /// Gets the name of a property.
        /// </summary>
        public string Name { get; private set; }

        /// <summary>
        /// Gets a display name for the property.
        /// </summary>
        /// <remarks>Keep in mind that this display name will return a display
        /// name defined during property definition. If you override property's
        /// display name, you need to retrieve it directly from <see cref="PropertyMetadata"/> instance.</remarks>
        public string DisplayName
        {
            get
            {
                var metadata = GetMetadata(OwnerType);
                return !string.IsNullOrEmpty(metadata.DisplayName) ? metadata.DisplayName : Name;
            }
        }

        /// <summary>
        /// Gets a CLR type of a property.
        /// </summary>
        public Type PropertyType { get; private set; }

        /// <summary>
        /// Gets an owner type.
        /// </summary>
        public Type OwnerType { get; private set; }

        /// <summary>
        /// Gets a value indicating if a property is read only.
        /// </summary>
        public bool IsReadOnly { get; private set; }

        /// <summary>
        /// Gets a value indicating whether a property is validated when set.
        /// </summary>
        public bool IsValidatable
        {
            get { return !ContainsOption(MetaPropertyOptions.NonValidatable); }
        }

        /// <summary>
        /// Gets a value indicating whether this meta property can be overriden.
        /// </summary>
        public bool CanOverrideMetadata
        {
            get { return !ContainsOption(MetaPropertyOptions.Sealed); }
        }

        /// <summary>
        /// Gets a value indicating the kind of a property.
        /// </summary>
        public MetaPropertyKind PropertyKind { get; private set; }

        /// <summary>
        /// Gets a value indicating whether a property is defined in a <see cref="PropertyScopeBase"/>.
        /// </summary>
        public bool IsScoped { get; private set; }

        /// <summary>
        /// Gets a collection of validation rules. You can only add new rules.
        /// </summary>
        internal RuleList ValidationRules { get; private set; }

        /// <summary>
        /// Internal index. Used by <see cref="MetaPropertyKind.Permanent"/>
        /// and <see cref="MetaPropertyKind.Transient"/> properties.
        /// </summary>
        internal int Index { get; set; }

        /// <summary>
        /// Internal callback for calculating value of a transient property.
        /// Used only by <see cref="MetaPropertyKind.Transient"/> properties.
        /// </summary>
        internal TransientPropertyCallback Callback { get; private set; }

        /// <summary>
        /// Stores transient properties which are dependent on this property. If this
        /// property changes, all dependent transient properties also changes.
        /// </summary>
        internal IList<MetaProperty> DependentTransientProperties { get; private set; }

        /// <summary>
        /// Returns metadata for a property represented by this meta property.
        /// </summary>
        /// <param name="type">Type for which metadata is being retreived.</param>
        /// <returns>
        /// Returns <see cref="PropertyMetadata"/> for a specified type. If the given
        /// type did not register any metadata, metadata for the most-derieved class
        /// that registered metadata will be returned.</returns>
        public PropertyMetadata GetMetadata(Type type)
        {
            if (PropertyKind == MetaPropertyKind.Dynamic ||
                PropertyKind == MetaPropertyKind.Attached)
                type = OwnerType;

            do
            {
                if (m_Metadata.ContainsKey(type))
                    return m_Metadata[type];

                type = type.BaseType;
            } while (type != null && type != typeof(PropertyContainer));

            return null;
        }

        /// <summary>
        /// Returns last registered <see cref="PropertyMetadata"/> instamce.
        /// </summary>
        /// <returns>Last registered <see cref="PropertyMetadata"/> instamce.</returns>
        public PropertyMetadata GetMetadata()
        {
            var type = TypeHelper.GetMostDerievedType(m_Metadata.Keys);
            return m_Metadata[type];
        }

        /// <summary>
        /// Returns a collection of meta properties which were registered for the given
        /// type and all of its base types.
        /// </summary>
        /// <param name="type">Type for which metadata hierarchy is being retreived.</param>
        /// <returns>Returns a collection of meta properties which were registered for the given
        /// type and all of its base types.</returns>
        public IEnumerable<PropertyMetadata> GetMetadataHierarchy(Type type)
        {
            if (PropertyKind == MetaPropertyKind.Dynamic ||
                PropertyKind == MetaPropertyKind.Attached)
                type = OwnerType;

            do
            {
                if (m_Metadata.ContainsKey(type))
                    yield return m_Metadata[type];

                type = type.BaseType;
            } while (type != null && type != typeof(PropertyContainer));
        }

        /// <summary>
        /// Overrides metadata for this meta property. Dynamic and attached properties does not support
        /// overriding metadata and will throw an exception on such an attempt.
        /// </summary>
        /// <param name="ownerType">Type which registers new metadata.</param>
        /// <param name="metadata">Metadata to be registered.</param>
        public void OverrideMetadata(Type ownerType, PropertyMetadata metadata)
        {
            if (!CanOverrideMetadata)
                throw new MetaPropertyException(string.Format(Res.ExceptionCannotOverrideMetadata, Name));

            if (PropertyKind == MetaPropertyKind.Dynamic)
                throw new MetaPropertyException(string.Format(Res.ExceptionCannotOverrideMetadataDynamic, Name));

            if (PropertyKind == MetaPropertyKind.Attached)
                throw new MetaPropertyException(string.Format(Res.ExceptionCannotOverrideMetadataAttached, Name));

            if (m_Metadata.ContainsKey(ownerType))
                throw new MetaPropertyException(string.Format(Res.ExceptionCannotOverrideMetadataForExistingType, ownerType.Name, Name));

            m_Metadata.Add(ownerType, metadata);
        }

        private bool ContainsOption(MetaPropertyOptions options)
        {
            return (m_Options & options) == options;
        }

        #region Private Serializations

        //[OnDeserialized]
        //private void OnDeserialized(StreamingContext context)
        //{
        //    m_Metadata = new Dictionary<Type, PropertyMetadata>();
        //    m_Metadata.Add(OwnerType, new PropertyMetadata());
        //}

        //[OnDeserializing]
        //private void OnDeserializing(StreamingContext context)
        //{
        //    m_Metadata = new Dictionary<Type, PropertyMetadata>();
        //    m_Metadata.Add(OwnerType, new PropertyMetadata());
        //}

        //private MetaProperty(SerializationInfo info, StreamingContext context)
        //{
        //    ValidationRules = new RuleList();
        //    DependentTransientProperties = new List<MetaProperty>();
        //    m_Metadata = new Dictionary<Type, PropertyMetadata>();

        //    Index = -1;
        //    Callback = null;
        //    PropertyKind = (MetaPropertyKind)info.GetValue("PropertyKind", typeof(MetaPropertyKind));
        //    IsReadOnly = (bool)info.GetValue("IsReadOnly", typeof(bool));
        //    OwnerType = (Type)info.GetValue("OwnerType", typeof(Type));
        //    PropertyType = (Type)info.GetValue("PropertyType", typeof(Type));
        //    Name = (string)info.GetValue("Name", typeof(string));
        //    m_Options = (MetaPropertyOptions)info.GetValue("Options", typeof(MetaPropertyOptions));

        //    for (int i = 0; i < info.MemberCount - 6; i++)
        //    {
        //        var tuple = (Tuple<Type, PropertyMetadata>)info.GetValue("M" + i, typeof(Tuple<Type, PropertyMetadata>));
        //        m_Metadata.Add(tuple.Value1, tuple.Value2);
        //    }
        //}

        //void ISerializable.GetObjectData(SerializationInfo info, StreamingContext context)
        //{
        //    var count = 0;
        //    foreach (var metadata in m_Metadata)
        //    {
        //        var tuple = new Tuple<Type, PropertyMetadata> { Value1 = metadata.Key, Value2 = metadata.Value };
        //        info.AddValue("M" + count, tuple);
        //        count++;
        //    }

        //    info.AddValue("Options", m_Options);
        //    info.AddValue("Name", Name);
        //    info.AddValue("PropertyType", PropertyType);
        //    info.AddValue("OwnerType", OwnerType);
        //    info.AddValue("IsReadOnly", IsReadOnly);
        //    info.AddValue("PropertyKind", PropertyKind);
        //}

        #endregion

        #region Public Equals / GetHashCode Implementation

        #region IEquatable Implementation

        bool IEquatable<MetaProperty>.Equals(MetaProperty other)
        {
            return Equals(this, other);
        }

        #endregion

        public override bool Equals(object obj)
        {
            if (ReferenceEquals(null, obj)) return false;
            if (ReferenceEquals(this, obj)) return true;
            if (obj.GetType() != typeof(MetaProperty)) return false;

            var other = (MetaProperty) obj;

            return Name.Equals(other.Name) &&
                   PropertyType.Equals(other.PropertyType) &&
                   OwnerType.Equals(other.OwnerType);
        }

        public override int GetHashCode()
        {
            unchecked
            {
                int hash = 13;
                hash = (hash * 7) + Name.GetHashCode();
                hash = (hash * 7) + PropertyType.GetHashCode();
                hash = (hash * 7) + OwnerType.GetHashCode();

                return hash;
            }
        }

        public static bool operator ==(MetaProperty left, MetaProperty right)
        {
            return Equals(left, right);
        }

        public static bool operator !=(MetaProperty left, MetaProperty right)
        {
            return !Equals(left, right);
        }

        #endregion
    }
}